package com.han.yrpc.core.consumer;

import com.han.yrpc.core.SPIUtils;
import com.han.yrpc.core.registry.ServiceDiscovery;
import com.han.yrpc.core.registry.ServiceInfo;
import com.han.yrpc.core.runtime.YRequest;
import com.han.yrpc.core.runtime.YResponse;
import com.han.yrpc.core.runtime.YrpcClient;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

@Slf4j
public class DynamicProxy implements InvocationHandler {

    private final Class<?> serviceInterface;
    private final ServiceDiscovery serviceDiscovery;
    private final YrpcClient yrpcClient;

    private static final String SERVICE_DISCOVERY = "com.han.yrpc.core.registry.ServiceDiscovery";
    private static final String YRPC_RUNTIME = "com.han.yrpc.core.runtime.YrpcClient";

    /**
     * 这个代理对象一定是我自己new出来的，怎么把ServiceDiscover动态的塞进来呢？
     * SPI是一种方式
     * 另外就是从spring容器中拿也行
     */
    public DynamicProxy(Class<?> serviceInterface) {
        this.serviceInterface = serviceInterface;
        Map<String, Object> mappings = SPIUtils.getHandlerMappings();
        String serviceDiscoveryClassPath = (String) mappings.get(SERVICE_DISCOVERY);
        String yrpcClientClassPath = (String) mappings.get(YRPC_RUNTIME);
        ServiceDiscovery discovery = null;
        YrpcClient client = null;
        try {
            /**
             * 这种SPI的方式的话，我每一个代理对象里面都要new一个ServiceDiscovery的实例，有点浪费。
             * 解决方式的话可以把ServiceDiscovery的实现类设计成单例的，但是我又无法保证这些实现类是单例的，
             * 这样可以使用单例工厂（SingletonFactory）来保证ServiceDiscovery的实现类都是单例的。
             *
             * 当然，不使用SPI的话，从Spring容器中拿也是没得问题的
             */
            Class<?> serviceDiscoveryClazz = Class.forName(serviceDiscoveryClassPath);
            discovery = (ServiceDiscovery) serviceDiscoveryClazz.getDeclaredConstructor().newInstance();

            Class<?> yrpcClientClazz = Class.forName(yrpcClientClassPath);
            client = (YrpcClient) yrpcClientClazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        serviceDiscovery = discovery;
        yrpcClient = client;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        ServiceInfo serviceInfo = serviceDiscovery.discover(method.getDeclaringClass().getName());

        YRequest request = YRequest.builder()
                .serviceName(serviceInterface.getName())
                .methodName(method.getName())
                .parameterTypes(method.getParameterTypes())
                .args(args)
                .serviceInfo(serviceInfo)
                .build();

        YResponse<Object> yResponse = yrpcClient.send(request); // todo  check response
        return yResponse.getData();
    }
}
